package inabit.compiler.parser.table;

import inabit.compiler.lexical.Token;
import inabit.compiler.lexical.TokenClass;
import inabit.compiler.parser.grammar.Production;
import inabit.compiler.parser.table.error.TableError;
import inabit.compiler.parser.table.error.UnexpectedTokenError;

import java.util.HashMap;
import java.util.Map;


/**The predictive table for analysis while parsing it returns a production
 * given the NonTerminal on stack and Terminal on input*/
/**@author Rafael Rocha*/
public class PredictiveTable {
	/**Represents an entry of the preditiveTable*/
	private class Tuple {
		private String var;
		private TokenClass ter;
		public Tuple(String var, TokenClass ter) {
			this.var = var;
			this.ter = ter;
		}
		public String getVariableLabel() {
			return this.var;
		}
		public TokenClass getTokenClass() {
			return this.ter;
		}
		@Override
		public int hashCode() {
			return (var.hashCode() + ter.hashCode())/2;
		}
		@Override
		public boolean equals(Object obj) {
			if(!(obj instanceof Tuple)) {
				return false;
			}
			Tuple t = (Tuple)obj;
			return var.equals(t.getVariableLabel()) && ter.equals(t.getTokenClass());
		}
	}
	
	private Map<Tuple, Production> preditiveTable;
	private Map<Tuple, TableError> errorTable;
	private TableError defaultError;
	
	public PredictiveTable() {
		this.preditiveTable = new HashMap<PredictiveTable.Tuple, Production>();
		this.errorTable = new HashMap<PredictiveTable.Tuple, TableError>();
		defaultError = new UnexpectedTokenError();
	}
	
	/**Set the production rule that must be returned when the NonTerminal with label <i>var</i> is on the 
	 * parser stack and the terminals with token classes specified in the <i>terminals</i> is on input
	 * @param*/
	public void addEntry(Production rule, String var, TokenClass... terminals) {
		for(TokenClass ter : terminals) {
			this.preditiveTable.put(new Tuple(var, ter), rule);
		}
	}
	
	/**Set the error that must be returned when the nonTerminal with label <i>var</i> is on the
	 * parser stack and one of the terminals with token classes specified in the <i>tkClasses</i> are on input*/
	public void addError(TableError errorMessage, String var, TokenClass... tkClasses) {
		for(TokenClass ter : tkClasses) {
			if(!this.preditiveTable.containsKey(new Tuple(var, ter))) {
				this.errorTable.put(new Tuple(var, ter), errorMessage);
			}
		}
	}
	
	/**Return the corresponding production to the situation where the nonTerminal with the specified <i>label</i>
	 * is on the symbol stack and token with the specified Token class <i>tkClass</i> is on the input */
	public Production getProduction(String label, TokenClass tkClass) {
		try {
			if(preditiveTable.containsKey(new Tuple(label, tkClass))) {
				return this.preditiveTable.get(new Tuple(label, tkClass)).getClass().newInstance();
			}else {
				return null;
			}
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**Return the corresponding error to the situation where the nonTerminal with the specified <i>label</i>
	 * is on the symbol stack and token with the specified Token class <i>tkClass</i> is on the input.<br><br>
	 * if not specified returns a default error and returns null if there's a production rule associated
	 * to this situation*/
	public String getErrorMessage(String var, TokenClass ter, Token tk) {
		Tuple tuple = new Tuple(var, ter);
		if(preditiveTable.containsKey(tuple)) {
			return null;
		}
		
		TableError tError = this.errorTable.get(tuple);
		
		if(tError != null) {
			return tError.errorMessage(tk);
		}else {
			return defaultError.errorMessage(tk);
		}
	}
}
